﻿// Sigma ASC 105 LED Panel Control Library - http://www.codeplex.com/SigmaASC105lib/
// Copyright (c) Michal A. Valasek - Altairis, 2008 - http://www.altairis.cz/
// This code is licensed under terms of the Microsoft Permissive License (Ms-PL).

using System;
using System.IO;
using System.Text;
using System.Globalization;

namespace Altairis.LedPanelControl {

    /// <summary>
    /// This class represents single message to be sent to LED panel.
    /// </summary>
    public class Message : IDisposable {
        MemoryStream messageStream;

        /// <summary>
        /// Initializes a new instance of the <see cref="Message"/> class with default line transition options.
        /// </summary>
        public Message()
            : this(LineTransitionOptions.Default) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="Message"/> class with specified line transition options.
        /// </summary>
        /// <param name="options">The line transition options.</param>
        public Message(LineTransitionOptions options) {
            this.messageStream = new MemoryStream();
            this.AppendLineTransitionOptions(options);
        }

        /// <summary>
        /// Appends given text to message. Line breaks in the text will be ignored.
        /// </summary>
        /// <param name="text">The text.</param>
        public void Write(string text) {
            if (this.disposed) throw new ObjectDisposedException("Message");
            if (string.IsNullOrEmpty(text)) return;
            this.AppendBuffer(Encoding.ASCII.GetBytes(RemoveDiacritics(text)));
        }

        /// <summary>
        /// Appends given text to message. Line breaks in the text will be converted to new lines with given line transition options.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="options">The line transition options.</param>
        public void Write(string text, LineTransitionOptions options) {
            if (this.disposed) throw new ObjectDisposedException("Message");
            if (string.IsNullOrEmpty(text)) return;

            string[] lines = text.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < lines.Length; i++) {
                this.AppendBuffer(Encoding.ASCII.GetBytes(RemoveDiacritics(lines[i])));
                if (i < lines.Length - 1) this.WriteLine(options);
            }
        }

        /// <summary>
        /// Appends special function to message.
        /// </summary>
        /// <param name="function">The special function.</param>
        public void Write(SpecialFunction function) {
            if (this.disposed) throw new ObjectDisposedException("Message");
            this.AppendBuffer(Common.ControlPrefix, (byte)function);
        }

        /// <summary>
        /// Writes the specified glyph.
        /// </summary>
        /// <param name="glyph">The glyph.</param>
        public void Write(Glyph glyph) {
            if (this.disposed) throw new ObjectDisposedException("Message");
            this.AppendBuffer(Common.ControlPrefix, (byte)glyph);
        }

        /// <summary>
        /// Writes the specified logo or cartoon.
        /// </summary>
        /// <param name="logo">The logo or cartoon.</param>
        public void Write(Logo logo) {
            if (this.disposed) throw new ObjectDisposedException("Message");
            this.AppendBuffer(Common.ControlPrefix, (byte)logo);
        }

        /// <summary>
        /// Appends new line to message, with the same transition options like last time.
        /// </summary>
        public void WriteLine() {
            this.WriteLine(LineTransitionOptions.Previous);
        }

        /// <summary>
        /// Appends new line to message, with specified transition options.
        /// </summary>
        /// <param name="options">The line transition options.</param>
        public void WriteLine(LineTransitionOptions options) {
            if (this.disposed) throw new ObjectDisposedException("Message");
            this.AppendBuffer(Common.NewLine);
            this.AppendLineTransitionOptions(options);
        }

        /// <summary>
        /// Sets the font type.
        /// </summary>
        /// <param name="font">The font type.</param>
        public void SetFont(Font font) {
            if (this.disposed) throw new ObjectDisposedException("Message");
            this.AppendBuffer(Common.ControlPrefix, (byte)font);
            this.LastFont = font;
        }

        /// <summary>
        /// Sets the color.
        /// </summary>
        /// <param name="color">The color.</param>
        public void SetColor(Color color) {
            if (this.disposed) throw new ObjectDisposedException("Message");
            this.AppendBuffer(Common.ControlPrefix, (byte)color);
            this.LastColor = color;
        }

        /// <summary>
        /// Gets the length of message in bytes, including lead-in and lead-out.
        /// </summary>
        /// <value>The length of message in bytes, including lead-in and lead-out.</value>
        public long Length {
            get {
                return this.messageStream.Length + Common.MessageLeadIn.Length + Common.MessageLeadOut.Length;
            }
        }

        /// <summary>
        /// Writes message to underlying stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        protected internal void WriteTo(Stream stream) {
            if (this.disposed) throw new ObjectDisposedException("Message");
            if (stream == null) throw new ArgumentNullException("stream");

            messageStream.WriteTo(stream);
        }

        // Properties holding last known status

        /// <summary>
        /// Gets the last known font.
        /// </summary>
        /// <value>The last known font.</value>
        public Font LastFont { get; private set; }

        /// <summary>
        /// Gets the last known color.
        /// </summary>
        /// <value>The last known color.</value>
        public Color LastColor { get; private set; }

        /// <summary>
        /// Gets the last known animation type.
        /// </summary>
        /// <value>The last known animation type.</value>
        public AnimationType LastAnimation { get; private set; }

        /// <summary>
        /// Gets the last known animation speed.
        /// </summary>
        /// <value>The last known animation speed.</value>
        public AnimationSpeed LastSpeed { get; private set; }

        // Helper functions

        /// <summary>
        /// Appends the line transition options.
        /// </summary>
        /// <param name="options">The options.</param>
        private void AppendLineTransitionOptions(LineTransitionOptions options) {


            // Specify animation type
            if (options.Animation == AnimationType.Previous) {
                this.AppendBuffer((byte)this.LastAnimation);
            }
            else {
                this.AppendBuffer((byte)options.Animation);
                this.LastAnimation = options.Animation;
            }

            // Specify animation speed
            if (options.Speed == AnimationSpeed.Previous) {
                this.AppendBuffer(Common.ControlPrefix, (byte)this.LastSpeed);
            }
            else {
                this.AppendBuffer(Common.ControlPrefix, (byte)options.Speed);
                this.LastSpeed = options.Speed;
            }

            // Specify color
            if (options.Color == Color.Previous) {
                this.AppendBuffer(Common.ControlPrefix, (byte)this.LastColor);
            }
            else {
                this.AppendBuffer(Common.ControlPrefix, (byte)options.Color);
                this.LastColor = options.Color;
            }

            // Specify font
            if (options.Font == Font.Previous) {
                this.AppendBuffer(Common.ControlPrefix, (byte)this.LastFont);
            }
            else {
                this.AppendBuffer(Common.ControlPrefix, (byte)options.Font);
                this.LastFont = options.Font;
            }
        }

        /// <summary>
        /// Appends the byte array buffer.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        private void AppendBuffer(params byte[] buffer) {
            this.messageStream.Write(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Removes the diacritic marks from given string, leaving only plain ASCII characters.
        /// </summary>
        /// <param name="s">The string.</param>
        /// <returns>Returns string with diacritic marks removed.</returns>
        private static string RemoveDiacritics(string s) {
            s = s.Normalize(NormalizationForm.FormD);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < s.Length; i++) {
                if (CharUnicodeInfo.GetUnicodeCategory(s[i]) != UnicodeCategory.NonSpacingMark) sb.Append(s[i]);
            }
            return sb.ToString();
        }

        #region IDisposable Members

        bool disposed = false;

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose() {
            if (!this.disposed) {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool disposing) {
            if (this.disposed) return;

            if (disposing) {
                this.messageStream.Dispose();
            }
            this.disposed = true;
        }

        #endregion
    }

    /// <summary>
    /// This class represents miscellaneous options for line transition.
    /// </summary>
    public class LineTransitionOptions {

        /// <summary>
        /// Initializes a new instance of the <see cref="LineTransitionOptions"/> class.
        /// </summary>
        public LineTransitionOptions() {
            this.Animation = AnimationType.Previous;
            this.Speed = AnimationSpeed.Previous;
            this.Font = Font.Previous;
            this.Color = Color.Previous;
        }

        /// <summary>
        /// Gets or sets the animation type.
        /// </summary>
        /// <value>The animation type.</value>
        public AnimationType Animation { get; set; }

        /// <summary>
        /// Gets or sets the animation speed.
        /// </summary>
        /// <value>The animation speed.</value>
        public AnimationSpeed Speed { get; set; }

        /// <summary>
        /// Gets or sets the font.
        /// </summary>
        /// <value>The font.</value>
        public Font Font { get; set; }

        /// <summary>
        /// Gets or sets the color.
        /// </summary>
        /// <value>The color.</value>
        public Color Color { get; set; }

        /// <summary>
        /// Default line transition options.
        /// </summary>
        public static readonly LineTransitionOptions Default = new LineTransitionOptions {
            Animation = AnimationType.Default,
            Color = Color.Default,
            Font = Font.Default,
            Speed = AnimationSpeed.Default
        };

        /// <summary>
        /// Line transition options will be the same like last time.
        /// </summary>
        public static readonly LineTransitionOptions Previous = new LineTransitionOptions();

    }

}
